/** @file    Session.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Session.cpp 2883 2010-08-09 14:51:56Z alex $
 *  @brief
 * File containing methods for the wosh::communication::Session class.
 * The header for this class can be found in Session.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <core/Session.h>
 #include <core/ObjectAllocator.h>
 #include <core/Utilities.h>
 #include <core/DataModels.h>
 #include <core/MessageFilter.h>
 #include <core/SessionManager.h>
 #include <core/SecurityManager.h>
 #include <core/User.h>


using namespace std;
namespace wosh {

 WOSH_REGISTER_INTERFACE(wosh::Session, "wosh::Object", 1.0, _static_Session )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Session::Session()
	: Object() {
	this->id = Utilities::randomInt64();
	this->id = _GET_Abs(this->id);
	Object::setName("Session", this->id);
	Object::getURI().setParent(URI(_SessionManager_URI));
	Object::getURI().setKernelLocal();
	Object::getURI().setName( Utilities::toString(this->id) );
	Object::getURI().registerGlobal();

	Log.setContext( Object::getName(), true );
	Log.setIndent( 3 );
	Log.setLevel( LOG_INFO );
Log.setLevel( LOG_VERBOSE );

	this->protocol = SessionManager::getDefaultProtocol();
	Log(LOG_DEBUG, " : Acquired default protocol [%p]", this->protocol );

	this->creation_ts = Utilities::std_time();
	this->state = STATE_UNKNOWN;

	this->usernameLogged = "";
	this->username = "";
	this->credentials = NULL;
	this->restrictions = Session::ALLOW_NOTHING;
	this->maxLoginRetries = 3;
	this->welcomeMsg = "Welcome to WOSH Session.";
	this->welcomeShow = true;
	updatePrompt_();

	this->currentLocation.setKernelLocal();
	this->currentBus = _Bus_ANY;
	this->lastInputTS = 0;

	this->historyMaxSize = 20;

	setState(STATE_CREATED);
 }

Session::~Session() {
	Log(LOG_DEBUG, ":~Session() Destroying.." );
	setState(STATE_CLOSED);

	if ( BusCore.isConnected() )
		BusCore.disconnect(false);
	Log(LOG_VERBOSE, " Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Session::setState( SESSION_STATE value ) {
	Log(LOG_VERBOSE, ":setState(%s)", getStateAsString(value) );
	this->stateLock.lockForWrite();
	this->state = value;
	this->stateLock.unLock();
	SessionManager::getInstance()->session_event( value, this );
	return WRET_OK;
 }

WRESULT Session::open( unsigned long ) {
	if ( this->protocol == NULL ) return WRET_ERR_INTERNAL;
	if ( this->usernameLogged.empty() ) {
		Log(LOG_CRITICAL, ":open() FAILED#%d Username not set!", WRET_ERR_ILLEGAL_USE );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( this->username.empty() )
		this->username = this->usernameLogged;
	Log(LOG_DEBUG, ":open() Connecting 'wosh.Bus.Core'.." );
	BusCore.setMessageHandler(this);
	WRESULT bus_connected = BusCore.connect( _Bus_Core, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_connected) ) {
		Log(LOG_CRITICAL, ":open() FAILED#%d Connecting Bus (wosh.Bus.Core)..", bus_connected );
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, ":open() Switching state.." );
	updatePrompt();
	return setState(STATE_OPENED);
 }

WRESULT Session::login() {
	if ( this->usernameLogged.size() < 3 ) {
		Log(LOG_CRITICAL, ":open(%s,%p) FAILED#%d Username not set!", this->usernameLogged.c_str(), this->credentials, WRET_ERR_ILLEGAL_USE );
		return WRET_ERR_ILLEGAL_USE;
	 }
	setState(STATE_LOGGING_CHK);
	WRESULT ret = SessionManager::getInstance()->session_authorize(this->usernameLogged, this->credentials, this);
	if ( WFAILED(ret) ) {
		Log(LOG_CRITICAL, ":open(%s,%p) FAILED#%d Authorizing session", this->usernameLogged.c_str(), this->credentials, ret );
		setState(STATE_CLOSED);
		return WRET_ERR_INTERNAL;
	 }
	Log(LOG_INFO, ":open(%s,%p) Logged in..", this->usernameLogged.c_str(), this->credentials );
	this->loggingLock.lockForWrite();
	this->username = this->usernameLogged;
	Permission new_perm = getPermission();
	new_perm.setOwner(this->username);
	setPermission(new_perm);
	this->loggingLock.unLock();
	setState(STATE_LOGGING_LOGGED);
	return WRET_OK;
 }

WRESULT Session::close( unsigned long ) {
	WRESULT bus_disconnected = BusCore.disconnect();// DEADLOCK! Bus::ACTION_DEFERRED);
	if ( WFAILED(bus_disconnected) )
		Log(LOG_WARNING, ":close() FAILED#%d DisConnecting Bus (wosh.Bus.Core)..", bus_disconnected );
	Log(LOG_INFO, ":close() Switching state.." );
	return setState(STATE_CLOSED);
 }

WRESULT Session::su( const std::string& user ) {
	if ( !getRestriction(Session::ALLOW_SU) ) return WRET_ERR_SECURITY;
	this->loggingLock.lockForWrite();
	if ( user.empty() ) {
		if ( this->username != User::root ) {
			this->username = User::root;
		 }
		else {
			return WRET_ERR_PARAM;
		 }
	 }
	else {
		if ( this->usernameLogged == User::root ) {
			this->username = user;
		 }
		else {
			return WRET_ERR_ILLEGAL_USE;
		 }
	 }
	this->loggingLock.unLock();
	return WRET_OK;
 }
		

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////// MESSAGING

WRESULT Session::parseInput( const std::string& user_input ) {
	setLastInputNow();
	if ( user_input.empty() ) return WRET_ERR_PARAM;
	if ( this->protocol == NULL ) return WRET_ERR_INTERNAL;
	if ( Log.isDebug() )
		Log(LOG_DEBUG, ":parseInput(%s)", user_input.c_str() );
	return this->protocol->parseInput( user_input, this );
 }

SecurityToken* Session::signMessage( Message* message, const SecurityToken* source_token ) const {
	if ( message == NULL ) return NULL;
	message->setSource(this); // 
	if ( source_token != NULL ) // we are forwarding a message, it's important to keep track of source security token
		return SecurityManager::getCredentialProxy().signMessage(message, source_token, this); // source token will be encapsulated
	if ( this->credentials == NULL ) // the bundle has a custom SecurityTokenProvider, lets use that one
		return SecurityManager::getCredentialImplicit().signMessage(message, this);
	return this->credentials->signMessage(message, getUsername());
 }

WRESULT Session::postMessage( Message* message ) {
	signMessage(message);
	BusCore.postMessage( message );
	return WRET_OK;
 }

void Session::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( !MessageFilter::isMessageTarget(message, getURI()) ) return;
	if ( message.getDestination().isBroadcast() ) return;
	if ( this->protocol == NULL ) return;
	this->protocol->renderMessage( &message, source, this );
 }

/////////////////////////////////////////////////////////////////////////////////////////////////// MESSAGING
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS

bool Session::getRestriction( SESSION_RESTRICTIONS flag ) const {
	MutexLockerRead(&this->currentLock); 
	return ( (this->restrictions & (flag)) == flag );
 }

void Session::setRestriction( SESSION_RESTRICTIONS flag, bool enabled ) {
	MutexLockerWrite(&this->currentLock); 
	if ( enabled )
		this->restrictions |= flag;
	else
		this->restrictions &= ~flag;
 }

void Session::setRestrictions( int flags ) {
	MutexLockerWrite(&this->currentLock); 
	this->restrictions = flags;
 }

void Session::setMaxLoginRetries( int value ) {
	MutexLockerWrite(&this->currentLock); 
	this->maxLoginRetries = value;
 }

void Session::setWelcomeMessage( const std::string& value ) {
	MutexLockerWrite(&this->loggingLock);
	this->welcomeMsg = value;
 }

void Session::setWelcomePrompted( bool show ) {
	MutexLockerWrite(&this->loggingLock);
	this->welcomeShow = show;
 }

void Session::setCurrentLocation( const URI& uri ) {
	MutexLockerWrite(&this->currentLock); 
	this->currentLocation = uri; updatePrompt();
 }

void Session::setCurrentBus( const std::string& value ) {
	MutexLockerWrite(&this->currentLock); 
	this->currentBus = value; updatePrompt();
 }

const std::string& Session::updatePrompt_() {
	if ( this->protocol == NULL ) return this->prompt;
	this->prompt = this->protocol->buildPrompt( this );
	return this->prompt;
 }

void Session::setLastInputNow() const {
	MutexLockerWrite(&this->currentLock); 
	this->lastInputTS = Utilities::std_time();
 }
 
void Session::changeToUserHome() {
	URI home;
	home.setKernelLocal();
	home.setPath("Users");
	home.setName(this->username);
	setCurrentLocation(home);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

DataFieldCollector<Session>* Session::getDataFields() {
	DataFieldCollector<Session>* fields = new DataFieldCollector<Session>();
	fields->add<long, Variant>(&Session::getID, "ID", 0);
	fields->add<const std::string&, Variant>(&Session::getName, "Name", 1);
	fields->add<const char*, Variant>(&Session::getClassName, "Type", 2);
	fields->add<const char*,Variant>(&Session::getStateAsString, "State", 3);
	fields->add<const std::string&,Variant>(&Session::getUsername, "Username", 4);
	fields->add<long,Variant_DT>(&Session::getLastInputTS, "LastInputTS", 5);
	//DataFieldSubWrapperT<Session, const SessionProtocol*, const char*, Variant>* dfwProtocolStr;
	//dfwProtocolStr = new DataFieldSubWrapperT<Session, const SessionProtocol*, const char*, Variant>(&Session::getProtocol, &SessionProtocol::getClassName, "ProtocolName", 6);
	//dfwProtocolStr->info.visible = false;
	//fields->add(dfwProtocolStr);
	fields->add<const std::string&,Variant>(&Session::getEntityID, "EntityID", 7, false);
	fields->add<const URI&,URI>(&Session::getURI, "URI", 8, false);
	fields->add<const Permission&,Permission>(&Session::getPermission, "Permission", 9, false);
	return fields;
 }

const char* Session::getStateAsString( Session::SESSION_STATE state ) {
	switch(state) {
		case STATE_CREATED:			return "CREATED";
		case STATE_LOGGING:			return "LOGGING";
		case STATE_LOGGING_USER:	return "LOGGING_USER";
		case STATE_LOGGING_PWD:		return "LOGGING_PWD";
		case STATE_LOGGING_CHK:		return "LOGGING_CHK";
		case STATE_LOGGING_LOGGED:	return "LOGGING_LOGGED";
		case STATE_OPENED:			return "OPENED";
		case STATE_CLOSED:			return "CLOSED";
		case STATE_FAILURE:			return "FAILURE";
		case STATE_UNKNOWN:
		default:					return "UNKNOWN";
	 }
 }

Session::SESSION_STATE Session::getStateFromString( const char* state ) {
	if ( state == NULL )						return STATE_UNKNOWN;
	if ( strcmp(state, "CREATED") == 0 )		return STATE_CREATED;
	if ( strcmp(state, "LOGGING") == 0 )		return STATE_LOGGING;
	if ( strcmp(state, "LOGGING_USER") == 0 )	return STATE_LOGGING_USER;
	if ( strcmp(state, "LOGGING_PWD") == 0 )	return STATE_LOGGING_PWD;
	if ( strcmp(state, "LOGGING_CHK") == 0 )	return STATE_LOGGING_CHK;
	if ( strcmp(state, "LOGGING_LOGGED") == 0 )	return STATE_LOGGING_LOGGED;
	if ( strcmp(state, "OPENED") == 0 )			return STATE_OPENED;
	if ( strcmp(state, "CLOSED") == 0 )			return STATE_CLOSED;
	if ( strcmp(state, "FAILURE") == 0 )		return STATE_FAILURE;
	return STATE_UNKNOWN;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::Session_ListChanged, "wosh::Fact", 1.0, _static_Session_ListChanged )

long Session_ListChanged::getSessionID() const {
	const ISerializable* item = getItem();
	if ( item != NULL && item->isKindOf<Session>() )
		return item->as<Session>()->getID();
	const Variant* var = getItemKey();
	if ( var == NULL ) return 0;
	return var->toLong(0);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
